Data Visualization - Mini Project 2

This project is aims to:

The main objective of this projects are to:

  1. Visualize the average number of goals scored by the away team and the home team on maps.
  2. Analyze the number of matches won by the home team and the away team as the outcome of the match.
  3. Determine the total number of goals scored in each match.
  4. Analyze the heart disease prediction dataset.
  5. Fit a model to the heart disease data and determine its coefficients and metrics.

Data Loading

The project starts by loading the necessary libraries. These libraries provide functions for data manipulation and visualization, respectively.

# Load libraries
library(tidyverse)
library(lubridate)
library(sf)
library(data.table)
library(fastDummies)
library(dotwhisker)
library(ROCR)
#Load the data

worldcup_matches <- read_csv("data/WorldCupMatches.csv")

str(worldcup_matches)
spc_tbl_ [4,572 × 20] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
 $ Year                : num [1:4572] 1930 1930 1930 1930 1930 1930 1930 1930 1930 1930 ...
 $ Datetime            : chr [1:4572] "13 Jul 1930 - 15:00" "13 Jul 1930 - 15:00" "14 Jul 1930 - 12:45" "14 Jul 1930 - 14:50" ...
 $ Stage               : chr [1:4572] "Group 1" "Group 4" "Group 2" "Group 3" ...
 $ Stadium             : chr [1:4572] "Pocitos" "Parque Central" "Parque Central" "Pocitos" ...
 $ City                : chr [1:4572] "Montevideo" "Montevideo" "Montevideo" "Montevideo" ...
 $ Home Team Name      : chr [1:4572] "France" "USA" "Yugoslavia" "Romania" ...
 $ Home Team Goals     : num [1:4572] 4 3 2 3 1 3 4 3 1 1 ...
 $ Away Team Goals     : num [1:4572] 1 0 1 1 0 0 0 0 0 0 ...
 $ Away Team Name      : chr [1:4572] "Mexico" "Belgium" "Brazil" "Peru" ...
 $ Win conditions      : chr [1:4572] NA NA NA NA ...
 $ Attendance          : num [1:4572] 4444 18346 24059 2549 23409 ...
 $ Half-time Home Goals: num [1:4572] 3 2 2 1 0 1 0 2 0 0 ...
 $ Half-time Away Goals: num [1:4572] 0 0 0 0 0 0 0 0 0 0 ...
 $ Referee             : chr [1:4572] "LOMBARDI Domingo (URU)" "MACIAS Jose (ARG)" "TEJADA Anibal (URU)" "WARNKEN Alberto (CHI)" ...
 $ Assistant 1         : chr [1:4572] "CRISTOPHE Henry (BEL)" "MATEUCCI Francisco (URU)" "VALLARINO Ricardo (URU)" "LANGENUS Jean (BEL)" ...
 $ Assistant 2         : chr [1:4572] "REGO Gilberto (BRA)" "WARNKEN Alberto (CHI)" "BALWAY Thomas (FRA)" "MATEUCCI Francisco (URU)" ...
 $ RoundID             : num [1:4572] 201 201 201 201 201 201 201 201 201 201 ...
 $ MatchID             : num [1:4572] 1096 1090 1093 1098 1085 ...
 $ Home Team Initials  : chr [1:4572] "FRA" "USA" "YUG" "ROU" ...
 $ Away Team Initials  : chr [1:4572] "MEX" "BEL" "BRA" "PER" ...
 - attr(*, "spec")=
  .. cols(
  ..   Year = col_double(),
  ..   Datetime = col_character(),
  ..   Stage = col_character(),
  ..   Stadium = col_character(),
  ..   City = col_character(),
  ..   `Home Team Name` = col_character(),
  ..   `Home Team Goals` = col_double(),
  ..   `Away Team Goals` = col_double(),
  ..   `Away Team Name` = col_character(),
  ..   `Win conditions` = col_character(),
  ..   Attendance = col_double(),
  ..   `Half-time Home Goals` = col_double(),
  ..   `Half-time Away Goals` = col_double(),
  ..   Referee = col_character(),
  ..   `Assistant 1` = col_character(),
  ..   `Assistant 2` = col_character(),
  ..   RoundID = col_double(),
  ..   MatchID = col_double(),
  ..   `Home Team Initials` = col_character(),
  ..   `Away Team Initials` = col_character()
  .. )
 - attr(*, "problems")=<externalptr> 

Data cleaning and processing

The data cleaning and processing steps include the following:

Empty rows has been removed from the dataset


# Remove rows with empty values
worldcup_matches <- worldcup_matches[!is.na(worldcup_matches$Year), ]

Converted Datetime column into Date column and Time column with appropriate format

# Convert the datetime column to a POSIXct object
worldcup_matches$Datetime <- as.POSIXct(worldcup_matches$Datetime, format = "%d %b %Y - %H:%M", tz = "UTC")
# Separate the datetime column into date and time
worldcup_matches$Date <- as.Date(worldcup_matches$Datetime)
worldcup_matches$Time <- format(worldcup_matches$Datetime, format = "%H:%M")

Renaming specific column names for clarity

# Assuming you want to rename the "Win conditions" column to "Outcome"
colnames(worldcup_matches)[6] <- "home_team_name"
colnames(worldcup_matches)[7] <- "home_team_goals"
colnames(worldcup_matches)[8] <- "away_team_goals"
colnames(worldcup_matches)[9] <- "away_team_name"
colnames(worldcup_matches)[10] <- "win_conditions"
colnames(worldcup_matches)[12] <- "half_time_home_goals"
colnames(worldcup_matches)[13] <- "half_time_away_goals"
colnames(worldcup_matches)[15] <- "assistant1"
colnames(worldcup_matches)[16] <- "assistant2"
colnames(worldcup_matches)[19] <- "home_team_initials"
colnames(worldcup_matches)[20] <- "away_team_initials"
colnames(worldcup_matches)
 [1] "Year"                 "Datetime"             "Stage"                "Stadium"              "City"                 "home_team_name"       "home_team_goals"      "away_team_goals"     
 [9] "away_team_name"       "win_conditions"       "Attendance"           "half_time_home_goals" "half_time_away_goals" "Referee"              "assistant1"           "assistant2"          
[17] "RoundID"              "MatchID"              "home_team_initials"   "away_team_initials"   "Date"                 "Time"                

Creating Goals column by adding home team goals and away team goals

# Create the "Goals" column
worldcup_matches$Goals <- worldcup_matches$home_team_goals + worldcup_matches$away_team_goals

# Create the "Match Outcome" column
worldcup_matches$outcome <- ifelse(worldcup_matches$home_team_goals > worldcup_matches$away_team_goals, "Home Team Win", "Away Team Win")
# Calculate summary statistics
summary(worldcup_matches$home_team_goals)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  0.000   1.000   2.000   1.811   3.000  10.000 
summary(worldcup_matches$away_team_goals)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  0.000   0.000   1.000   1.022   2.000   7.000 

From above summary statistics we can determine that maximum number of goals scored by home team are 10 and Away team scored 7.

Cleaning the dataset by replacing the old names of country with current names to map them with number of goals scored.

worldcup_matches = worldcup_matches %>% mutate(home_country=case_when(home_team_name %like% 'China' ~ "China"
                                                                      , home_team_name=="C�te d'Ivoire" ~ 'Ivory Coast' 
                                                                      , home_team_name %like% "Czech" ~ 'Czechia' 
                                                                      , home_team_name=="England" ~ 'United Kingdom'
                                                                      , home_team_name=="Scotland" ~ 'United Kingdom'
                                                                      , home_team_name %like% "German" ~ 'Germany' 
                                                                      , home_team_name %like% "Iran" ~ 'Iran'
                                                                      , home_team_name %like% "Korea DPR" ~ 'North Korea'
                                                                      , home_team_name %like% "Korea" ~ 'South Korea'
                                                                      , home_team_name %like% "Ireland" ~ 'Ireland'
                                                                      , home_team_name %like% "Serbia" ~ 'Republic of Serbia'
                                                                      , home_team_name %like% "Yugoslavia" ~ 'Republic of Serbia'
                                                                      , home_team_name %like% "Soviet Union" ~ 'Russia'
                                                                      , home_team_name %like% "USA" ~ 'United States of America'
                                                                      , home_team_name %like% "Wales" ~ 'United Kingdom'
                                                                      , home_team_name %like% "Zaire" ~ 'Republic of the Congo'
                                                                      , home_team_name %like% "Bosnia" ~ 'Bosnia and Herzegovina'
                                                                      , home_team_name %like% "Trinidad and Tobago" ~ "Trinidad and Tobago"
                                                                      , home_team_name %like% "United Arab Emirates" ~ "United Arab Emirates"
                                                                      , home_team_name %like% "Dutch East Indies" ~ "Indonesia"
                                                                      , TRUE ~ home_team_name
                                                                      )
                                               , away_country=case_when(away_team_name %like% 'China' ~ "China"
                                                                      , away_team_name=="C�te d'Ivoire" ~ 'Ivory Coast' 
                                                                      , away_team_name %like% "Czech" ~ 'Czechia' 
                                                                      , away_team_name=="England" ~ 'United Kingdom'
                                                                      , away_team_name=="Scotland" ~ 'United Kingdom'
                                                                      , away_team_name %like% "German" ~ 'Germany' 
                                                                      , away_team_name %like% "Iran" ~ 'Iran'
                                                                      , away_team_name %like% "Korea DPR" ~ 'North Korea'
                                                                      , away_team_name %like% "Korea" ~ 'South Korea'
                                                                      , away_team_name %like% "Ireland" ~ 'Ireland'
                                                                      , away_team_name %like% "Serbia" ~ 'Republic of Serbia'
                                                                      , away_team_name %like% "Yugoslavia" ~ 'Republic of Serbia'
                                                                      , away_team_name %like% "Soviet Union" ~ 'Russia'
                                                                      , away_team_name %like% "USA" ~ 'United States of America'
                                                                      , away_team_name %like% "Wales" ~ 'United Kingdom'
                                                                      , away_team_name %like% "Zaire" ~ 'Republic of the Congo'
                                                                      , away_team_name %like% "Bosnia" ~ 'Bosnia and Herzegovina'
                                                                      , away_team_name %like% "Trinidad and Tobago" ~ "Trinidad and Tobago"
                                                                      , away_team_name %like% "United Arab Emirates" ~ "United Arab Emirates"
                                                                      , away_team_name %like% "Dutch East Indies" ~ "Indonesia"
                                                                      , TRUE ~ away_team_name
                                                                      ) 
                                      )

Visualization of Average number of Away team goals and Home team goals in Maps

The average number of goals scored by the away team is visualized on a map using the world shapefile data from Natural Earth. The “average_goals” variable calculated from the World Cup matches data by aggregating the home team and away team goals based on the country and marked them on map by grouping with country.

world_shapes <- read_sf("data/ne_110m_admin_0_countries/ne_110m_admin_0_countries.shp")

worldcup_away_goals <- worldcup_matches  %>% group_by(away_country) %>%summarise(average_goals = mean(away_team_goals))

away_spatial_data <- world_shapes %>%
  left_join(worldcup_away_goals, by = c("ADMIN" = "away_country")) %>%
  filter(ISO_A3 != "ATA") 



# Plot the map
ggplot(away_spatial_data, aes(group = ADMIN)) +
  geom_sf(aes(fill = average_goals)) +
  coord_sf(crs = "+proj=robin") +
  scale_fill_gradient(low = "blue", high = "red", na.value = "gray", name = "Away Team Goals") +
  ggtitle("Average Number of Away Team Goals in World Cup Matches") +
  theme_minimal() +
  theme(plot.title = element_text(size = 18, face = "bold"),
        legend.title = element_text(size = 12, face = "bold"),
        legend.text = element_text(size = 10),
        legend.position = "bottom",
        panel.grid = element_blank(),
        axis.text = element_blank(),
        axis.title = element_blank())

NA
NA

From the above map we can see that Germany scored highest avaerage number of goals in the worldcup matches followed by Netherlands and Brazil.


worldcup_home_goals <- worldcup_matches  %>% group_by(home_country) %>%summarise(average_goals = mean(home_team_goals))


home_spatial_data <- world_shapes %>%
  left_join(worldcup_home_goals, by = c("ADMIN" = "home_country")) %>%
  filter(ISO_A3 != "ATA") 



# Plot the map
 ggplot(home_spatial_data, aes(group = ADMIN)) +
  geom_sf(aes(fill = average_goals)) +
   coord_sf(crs = "+proj=robin") +
  scale_fill_gradient(low = "blue", high = "red", na.value = "gray", name = "Home Team Goals") +
  labs(title = "Average Number of Goals Scored by Home Teams in World Cup Matches") +
  theme_minimal() +
  theme(plot.title = element_text(size = 18, face = "bold"),
        legend.title = element_text(size = 12, face = "bold"),
        legend.text = element_text(size = 10),
        legend.position = "bottom",
        panel.grid = element_blank(),
        axis.text = element_blank(),
        axis.title = element_blank())

From the above map we can see that Turkey scored highest avaerage number of goals in the worldcup matches followed by Hungary.

Analyze the number of matches won by Home team and Away team as outcome of the match

# Bar chart of match outcome
b <- ggplot(matches_countries_cleaned, aes(x = outcome, fill = outcome)) +
  geom_bar( width = 0.3) +
  labs(title = "Match Outcome",
       x = "Outcome", y = "Count") +
  scale_fill_manual(values = c("Home Team Win" = "steelblue", "Away Team Win" = "salmon")) 

ggplotly(b)  

The total number of goals scored in each year from 1930 to 2014 is visualized using a bar chart. The chart depicts the trend of goals scored over time.

# Create the bar chart
Goals_plot <- ggplot(matches_countries_cleaned, aes(x = Year, y = Goals)) +
  geom_bar(stat = "identity", fill = "steelblue") +
  labs(title = "Total Goals Scored Every Year (1930-2014)",
       x = "Year", y = "Goals") +
  theme_minimal()

ggplotly(Goals_plot)
NA

As we can see from above plot, there are more number of goals scored in the year 2000. The worldcup games has been held every four years, except for the years 1942 and 1946, during World War II. due that we dont see any data for that period in the plot.

Worldcup_goals_by_year <- matches_countries_cleaned %>%
  group_by(Year) %>%
  mutate(total_goals = sum(Goals))



# Line plot: Number of goals over different years

l <- ggplot(Worldcup_goals_by_year, aes(x = Year, y = total_goals)) +
  geom_line(color="blue") +
  xlab("Year") + ylab("Number of Goals") +
  ggtitle("Number of Goals Over Different Years") +
  theme_minimal()

ggplotly(l)
NA

The above plot shows us the number of goals scored in world cup over the years 1930 to 2014.

Heart disease data set analysis and pre-processing


heart_data <- drop_na(read.csv("data/Heart.csv")[, -1])

str(heart_data)
'data.frame':   297 obs. of  14 variables:
 $ Age      : int  63 67 67 37 41 56 62 57 63 53 ...
 $ Sex      : int  1 1 1 1 0 1 0 0 1 1 ...
 $ ChestPain: chr  "typical" "asymptomatic" "asymptomatic" "nonanginal" ...
 $ RestBP   : int  145 160 120 130 130 120 140 120 130 140 ...
 $ Chol     : int  233 286 229 250 204 236 268 354 254 203 ...
 $ Fbs      : int  1 0 0 0 0 0 0 0 0 1 ...
 $ RestECG  : int  2 2 2 0 2 0 2 0 2 2 ...
 $ MaxHR    : int  150 108 129 187 172 178 160 163 147 155 ...
 $ ExAng    : int  0 1 1 0 0 0 0 1 0 1 ...
 $ Oldpeak  : num  2.3 1.5 2.6 3.5 1.4 0.8 3.6 0.6 1.4 3.1 ...
 $ Slope    : int  3 2 2 3 1 1 3 1 2 3 ...
 $ Ca       : int  0 3 2 0 0 0 2 0 1 0 ...
 $ Thal     : chr  "fixed" "normal" "reversable" "normal" ...
 $ AHD      : chr  "No" "Yes" "Yes" "No" ...

The dataset is preprocessed by converting the “AHD” (heart disease diagnosis) column into binary values (0 for “No” and 1 for “Yes”) becasue the target variable is in character format. .

heart_data$AHD<-ifelse(heart_data$AHD=="Yes",1,0)
summary(heart_data)
      Age             Sex          ChestPain             RestBP           Chol            Fbs            RestECG           MaxHR           ExAng           Oldpeak          Slope      
 Min.   :29.00   Min.   :0.0000   Length:297         Min.   : 94.0   Min.   :126.0   Min.   :0.0000   Min.   :0.0000   Min.   : 71.0   Min.   :0.0000   Min.   :0.000   Min.   :1.000  
 1st Qu.:48.00   1st Qu.:0.0000   Class :character   1st Qu.:120.0   1st Qu.:211.0   1st Qu.:0.0000   1st Qu.:0.0000   1st Qu.:133.0   1st Qu.:0.0000   1st Qu.:0.000   1st Qu.:1.000  
 Median :56.00   Median :1.0000   Mode  :character   Median :130.0   Median :243.0   Median :0.0000   Median :1.0000   Median :153.0   Median :0.0000   Median :0.800   Median :2.000  
 Mean   :54.54   Mean   :0.6768                      Mean   :131.7   Mean   :247.4   Mean   :0.1448   Mean   :0.9966   Mean   :149.6   Mean   :0.3266   Mean   :1.056   Mean   :1.603  
 3rd Qu.:61.00   3rd Qu.:1.0000                      3rd Qu.:140.0   3rd Qu.:276.0   3rd Qu.:0.0000   3rd Qu.:2.0000   3rd Qu.:166.0   3rd Qu.:1.0000   3rd Qu.:1.600   3rd Qu.:2.000  
 Max.   :77.00   Max.   :1.0000                      Max.   :200.0   Max.   :564.0   Max.   :1.0000   Max.   :2.0000   Max.   :202.0   Max.   :1.0000   Max.   :6.200   Max.   :3.000  
       Ca             Thal                AHD        
 Min.   :0.0000   Length:297         Min.   :0.0000  
 1st Qu.:0.0000   Class :character   1st Qu.:0.0000  
 Median :0.0000   Mode  :character   Median :0.0000  
 Mean   :0.6768                      Mean   :0.4613  
 3rd Qu.:1.0000                      3rd Qu.:1.0000  
 Max.   :3.0000                      Max.   :1.0000  

Since the Chestpain and Thal are the catagorical variables creating dummy variables into numeric variable to fit the model using those variables.

mdl_data = dummy_cols(heart_data, select_columns=c( "ChestPain", "Thal"), remove_selected_columns=TRUE)

Model Fitting and Coefficient Plot

A logistic regression model is fitted to the heart disease data using the glm() function. The model’s coefficients and summary statistics are displayed. Additionally, a coefficient plot is generated to visualize the coefficients.

mdl = glm(AHD ~., family=binomial(link='logit'), data=mdl_data)

Summary for the model coefficients,

summary(mdl)

Call:
glm(formula = AHD ~ ., family = binomial(link = "logit"), data = mdl_data)

Coefficients: (2 not defined because of singularities)
                        Estimate Std. Error z value Pr(>|z|)    
(Intercept)            -4.544716   2.978667  -1.526 0.127071    
Age                    -0.012296   0.024664  -0.499 0.618120    
Sex                     1.431422   0.513185   2.789 0.005282 ** 
RestBP                  0.023981   0.011110   2.159 0.030889 *  
Chol                    0.004930   0.003944   1.250 0.211306    
Fbs                    -0.610758   0.599184  -1.019 0.308052    
RestECG                 0.255433   0.189565   1.347 0.177829    
MaxHR                  -0.021281   0.010821  -1.967 0.049224 *  
ExAng                   0.739431   0.434687   1.701 0.088931 .  
Oldpeak                 0.353095   0.230102   1.535 0.124903    
Slope                   0.670508   0.371616   1.804 0.071184 .  
Ca                      1.269290   0.271304   4.678 2.89e-06 ***
ChestPain_asymptomatic  2.006802   0.652608   3.075 0.002105 ** 
ChestPain_nonanginal    0.202175   0.648718   0.312 0.755304    
ChestPain_nontypical    1.071153   0.753902   1.421 0.155371    
ChestPain_typical             NA         NA      NA       NA    
Thal_fixed             -1.429947   0.783279  -1.826 0.067912 .  
Thal_normal            -1.441377   0.418558  -3.444 0.000574 ***
Thal_reversable               NA         NA      NA       NA    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 409.95  on 296  degrees of freedom
Residual deviance: 194.83  on 280  degrees of freedom
AIC: 228.83

Number of Fisher Scoring iterations: 6

Model Coefficient plot

dwplot(mdl)

From above coefficient plot and summary we can clearly identify Sex, Ca, ChestPain_asymptomic and Thal_normal are the most siginificant varibales for the model prediction.

Area Under ROC curve

y_hat  = predict(mdl, mdl_data)
pr = prediction(y_hat, mdl_data$AHD)
auc <- performance(pr, measure = "auc")
title = paste('AUC: ', auc@y.values[[1]])
prf <- performance(pr, measure = "tpr", x.measure = "fpr")
plot(prf, main=title)

The area under the receiver operating characteristic (ROC) curve is calculated to evaluate the model’s performance. The ROC is a metric used to evaluate the performance of a classification model. The ROC curve is created by plotting the True Positive Rate (Sensitivity) against the False Positive Rate (1 - Specificity) at various classification thresholds.

AUC-ROC > 0.5: The model performs better than random guessing. The higher the value, the better the performance.

As we can see this model generated AUC-ROC = 0.933 which is a good prediction.

Conclusion

The project successfully analyzes World Cup matches data and visualizes the average number of goals scored by the home team and away team on maps. Additionally, it performs model fitting and visualization on heart disease diagnostic data, providing insights into heart disease prediction. The visualizations and analyses contribute to a better understanding of the datasets and their patterns.

LS0tDQp0aXRsZTogIkRhdGEgVmlzdWFsaXphdGlvbiBNaW5pLVByb2plY3QgMiINCmF1dGhvcjogIk1vd25pa2EgS29uYW1hbmVuaSAtIG1rb25hbWFuZW5pNDUwMUBmbG9yaWRhcG9seS5lZHUiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQojIERhdGEgVmlzdWFsaXphdGlvbiAtIE1pbmkgUHJvamVjdCAyDQoNClRoaXMgcHJvamVjdCBpcyBhaW1zIHRvOg0KDQoqIEFuYWx5emUgdGhlIFdvcmxkIGN1cCBtYXRjaGVzIGRhdGEgZnJvbSAxOTMwIHRvIDIwMTQuIFRoZSBkYXRhc2V0IHVzZWQgZm9yIGFuYWx5c2lzIGlzICJXb3JsZEN1cE1hdGNoZXMuY3N2Ii4NCg0KKiBWaXN1YWxpemF0aW9uIG9mIE1vZGVsIGZpdHRpbmcgb24gSGVhcnQgZGlzZWFzZSBkaWFnbm9zdGljIGRhdGEgYW5kIGl0cyBjb2VmZmljaWVudCBwbG90Lg0KDQpUaGUgbWFpbiBvYmplY3RpdmUgb2YgdGhpcyBwcm9qZWN0cyBhcmUgdG86DQoNCjEuIFZpc3VhbGl6ZSB0aGUgYXZlcmFnZSBudW1iZXIgb2YgZ29hbHMgc2NvcmVkIGJ5IHRoZSBhd2F5IHRlYW0gYW5kIHRoZSBob21lIHRlYW0gb24gbWFwcy4NCjIuIEFuYWx5emUgdGhlIG51bWJlciBvZiBtYXRjaGVzIHdvbiBieSB0aGUgaG9tZSB0ZWFtIGFuZCB0aGUgYXdheSB0ZWFtIGFzIHRoZSBvdXRjb21lIG9mIHRoZSBtYXRjaC4NCjMuIERldGVybWluZSB0aGUgdG90YWwgbnVtYmVyIG9mIGdvYWxzIHNjb3JlZCBpbiBlYWNoIG1hdGNoLg0KNC4gQW5hbHl6ZSB0aGUgaGVhcnQgZGlzZWFzZSBwcmVkaWN0aW9uIGRhdGFzZXQuDQo1LiBGaXQgYSBtb2RlbCB0byB0aGUgaGVhcnQgZGlzZWFzZSBkYXRhIGFuZCBkZXRlcm1pbmUgaXRzIGNvZWZmaWNpZW50cyBhbmQgbWV0cmljcy4gDQoNCg0KIyMgRGF0YSBMb2FkaW5nDQpUaGUgcHJvamVjdCBzdGFydHMgYnkgbG9hZGluZyB0aGUgbmVjZXNzYXJ5IGxpYnJhcmllcy4gVGhlc2UgbGlicmFyaWVzIHByb3ZpZGUgZnVuY3Rpb25zIGZvciBkYXRhIG1hbmlwdWxhdGlvbiBhbmQgdmlzdWFsaXphdGlvbiwgcmVzcGVjdGl2ZWx5LiANCg0KYGBge3IsICwgcmVzdWx0cz0naGlkZScsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQojIExvYWQgbGlicmFyaWVzDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkobHVicmlkYXRlKQ0KbGlicmFyeShzZikNCmxpYnJhcnkoZGF0YS50YWJsZSkNCmxpYnJhcnkoZmFzdER1bW1pZXMpDQpsaWJyYXJ5KGRvdHdoaXNrZXIpDQpsaWJyYXJ5KFJPQ1IpDQpgYGANCg0KYGBge3IsICwgbWVzc2FnZT1GQUxTRSwgd2FybmluZyA9IEZBTFNFfQ0KI0xvYWQgdGhlIGRhdGENCg0Kd29ybGRjdXBfbWF0Y2hlcyA8LSByZWFkX2NzdigiZGF0YS9Xb3JsZEN1cE1hdGNoZXMuY3N2IikNCg0Kc3RyKHdvcmxkY3VwX21hdGNoZXMpDQoNCmBgYA0KDQojIyBEYXRhIGNsZWFuaW5nIGFuZCBwcm9jZXNzaW5nDQoNClRoZSBkYXRhIGNsZWFuaW5nIGFuZCBwcm9jZXNzaW5nIHN0ZXBzIGluY2x1ZGUgdGhlIGZvbGxvd2luZzoNCg0KRW1wdHkgcm93cyBoYXMgYmVlbiByZW1vdmVkIGZyb20gdGhlIGRhdGFzZXQNCmBgYHtyLCBlY2hvPVRSVUV9DQoNCiMgUmVtb3ZlIHJvd3Mgd2l0aCBlbXB0eSB2YWx1ZXMNCndvcmxkY3VwX21hdGNoZXMgPC0gd29ybGRjdXBfbWF0Y2hlc1shaXMubmEod29ybGRjdXBfbWF0Y2hlcyRZZWFyKSwgXQ0KDQpgYGANCg0KQ29udmVydGVkIERhdGV0aW1lIGNvbHVtbiBpbnRvIERhdGUgY29sdW1uIGFuZCBUaW1lIGNvbHVtbiB3aXRoIGFwcHJvcHJpYXRlIGZvcm1hdCANCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nID0gRkFMU0V9DQojIENvbnZlcnQgdGhlIGRhdGV0aW1lIGNvbHVtbiB0byBhIFBPU0lYY3Qgb2JqZWN0DQp3b3JsZGN1cF9tYXRjaGVzJERhdGV0aW1lIDwtIGFzLlBPU0lYY3Qod29ybGRjdXBfbWF0Y2hlcyREYXRldGltZSwgZm9ybWF0ID0gIiVkICViICVZIC0gJUg6JU0iLCB0eiA9ICJVVEMiKQ0KIyBTZXBhcmF0ZSB0aGUgZGF0ZXRpbWUgY29sdW1uIGludG8gZGF0ZSBhbmQgdGltZQ0Kd29ybGRjdXBfbWF0Y2hlcyREYXRlIDwtIGFzLkRhdGUod29ybGRjdXBfbWF0Y2hlcyREYXRldGltZSkNCndvcmxkY3VwX21hdGNoZXMkVGltZSA8LSBmb3JtYXQod29ybGRjdXBfbWF0Y2hlcyREYXRldGltZSwgZm9ybWF0ID0gIiVIOiVNIikNCg0KYGBgDQoNClJlbmFtaW5nIHNwZWNpZmljIGNvbHVtbiBuYW1lcyBmb3IgY2xhcml0eQ0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZyA9IEZBTFNFfQ0KIyBBc3N1bWluZyB5b3Ugd2FudCB0byByZW5hbWUgdGhlICJXaW4gY29uZGl0aW9ucyIgY29sdW1uIHRvICJPdXRjb21lIg0KY29sbmFtZXMod29ybGRjdXBfbWF0Y2hlcylbNl0gPC0gImhvbWVfdGVhbV9uYW1lIg0KY29sbmFtZXMod29ybGRjdXBfbWF0Y2hlcylbN10gPC0gImhvbWVfdGVhbV9nb2FscyINCmNvbG5hbWVzKHdvcmxkY3VwX21hdGNoZXMpWzhdIDwtICJhd2F5X3RlYW1fZ29hbHMiDQpjb2xuYW1lcyh3b3JsZGN1cF9tYXRjaGVzKVs5XSA8LSAiYXdheV90ZWFtX25hbWUiDQpjb2xuYW1lcyh3b3JsZGN1cF9tYXRjaGVzKVsxMF0gPC0gIndpbl9jb25kaXRpb25zIg0KY29sbmFtZXMod29ybGRjdXBfbWF0Y2hlcylbMTJdIDwtICJoYWxmX3RpbWVfaG9tZV9nb2FscyINCmNvbG5hbWVzKHdvcmxkY3VwX21hdGNoZXMpWzEzXSA8LSAiaGFsZl90aW1lX2F3YXlfZ29hbHMiDQpjb2xuYW1lcyh3b3JsZGN1cF9tYXRjaGVzKVsxNV0gPC0gImFzc2lzdGFudDEiDQpjb2xuYW1lcyh3b3JsZGN1cF9tYXRjaGVzKVsxNl0gPC0gImFzc2lzdGFudDIiDQpjb2xuYW1lcyh3b3JsZGN1cF9tYXRjaGVzKVsxOV0gPC0gImhvbWVfdGVhbV9pbml0aWFscyINCmNvbG5hbWVzKHdvcmxkY3VwX21hdGNoZXMpWzIwXSA8LSAiYXdheV90ZWFtX2luaXRpYWxzIg0KY29sbmFtZXMod29ybGRjdXBfbWF0Y2hlcykNCg0KYGBgDQpDcmVhdGluZyBHb2FscyBjb2x1bW4gYnkgYWRkaW5nIGhvbWUgdGVhbSBnb2FscyBhbmQgYXdheSB0ZWFtIGdvYWxzIA0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZyA9IEZBTFNFfQ0KIyBDcmVhdGUgdGhlICJHb2FscyIgY29sdW1uDQp3b3JsZGN1cF9tYXRjaGVzJEdvYWxzIDwtIHdvcmxkY3VwX21hdGNoZXMkaG9tZV90ZWFtX2dvYWxzICsgd29ybGRjdXBfbWF0Y2hlcyRhd2F5X3RlYW1fZ29hbHMNCg0KIyBDcmVhdGUgdGhlICJNYXRjaCBPdXRjb21lIiBjb2x1bW4NCndvcmxkY3VwX21hdGNoZXMkb3V0Y29tZSA8LSBpZmVsc2Uod29ybGRjdXBfbWF0Y2hlcyRob21lX3RlYW1fZ29hbHMgPiB3b3JsZGN1cF9tYXRjaGVzJGF3YXlfdGVhbV9nb2FscywgIkhvbWUgVGVhbSBXaW4iLCAiQXdheSBUZWFtIFdpbiIpDQpgYGANCg0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZyA9IEZBTFNFfQ0KIyBDYWxjdWxhdGUgc3VtbWFyeSBzdGF0aXN0aWNzDQpzdW1tYXJ5KHdvcmxkY3VwX21hdGNoZXMkaG9tZV90ZWFtX2dvYWxzKQ0Kc3VtbWFyeSh3b3JsZGN1cF9tYXRjaGVzJGF3YXlfdGVhbV9nb2FscykNCg0KYGBgDQoNCkZyb20gYWJvdmUgc3VtbWFyeSBzdGF0aXN0aWNzIHdlIGNhbiBkZXRlcm1pbmUgdGhhdCBtYXhpbXVtIG51bWJlciBvZiBnb2FscyBzY29yZWQgYnkgaG9tZSB0ZWFtIGFyZSAxMCBhbmQgQXdheSB0ZWFtIHNjb3JlZCA3LiAgIA0KDQpDbGVhbmluZyB0aGUgZGF0YXNldCBieSByZXBsYWNpbmcgdGhlIG9sZCBuYW1lcyBvZiBjb3VudHJ5IHdpdGggY3VycmVudCBuYW1lcyB0byBtYXAgdGhlbSB3aXRoIG51bWJlciBvZiBnb2FscyBzY29yZWQuIA0KYGBge3J9DQp3b3JsZGN1cF9tYXRjaGVzID0gd29ybGRjdXBfbWF0Y2hlcyAlPiUgbXV0YXRlKGhvbWVfY291bnRyeT1jYXNlX3doZW4oaG9tZV90ZWFtX25hbWUgJWxpa2UlICdDaGluYScgfiAiQ2hpbmEiDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLCBob21lX3RlYW1fbmFtZT09IkPvv710ZSBkJ0l2b2lyZSIgfiAnSXZvcnkgQ29hc3QnIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICwgaG9tZV90ZWFtX25hbWUgJWxpa2UlICJDemVjaCIgfiAnQ3plY2hpYScgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLCBob21lX3RlYW1fbmFtZT09IkVuZ2xhbmQiIH4gJ1VuaXRlZCBLaW5nZG9tJw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICwgaG9tZV90ZWFtX25hbWU9PSJTY290bGFuZCIgfiAnVW5pdGVkIEtpbmdkb20nDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLCBob21lX3RlYW1fbmFtZSAlbGlrZSUgIkdlcm1hbiIgfiAnR2VybWFueScgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLCBob21lX3RlYW1fbmFtZSAlbGlrZSUgIklyYW4iIH4gJ0lyYW4nDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLCBob21lX3RlYW1fbmFtZSAlbGlrZSUgIktvcmVhIERQUiIgfiAnTm9ydGggS29yZWEnDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLCBob21lX3RlYW1fbmFtZSAlbGlrZSUgIktvcmVhIiB+ICdTb3V0aCBLb3JlYScNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAsIGhvbWVfdGVhbV9uYW1lICVsaWtlJSAiSXJlbGFuZCIgfiAnSXJlbGFuZCcNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAsIGhvbWVfdGVhbV9uYW1lICVsaWtlJSAiU2VyYmlhIiB+ICdSZXB1YmxpYyBvZiBTZXJiaWEnDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLCBob21lX3RlYW1fbmFtZSAlbGlrZSUgIll1Z29zbGF2aWEiIH4gJ1JlcHVibGljIG9mIFNlcmJpYScNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAsIGhvbWVfdGVhbV9uYW1lICVsaWtlJSAiU292aWV0IFVuaW9uIiB+ICdSdXNzaWEnDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLCBob21lX3RlYW1fbmFtZSAlbGlrZSUgIlVTQSIgfiAnVW5pdGVkIFN0YXRlcyBvZiBBbWVyaWNhJw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICwgaG9tZV90ZWFtX25hbWUgJWxpa2UlICJXYWxlcyIgfiAnVW5pdGVkIEtpbmdkb20nDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLCBob21lX3RlYW1fbmFtZSAlbGlrZSUgIlphaXJlIiB+ICdSZXB1YmxpYyBvZiB0aGUgQ29uZ28nDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLCBob21lX3RlYW1fbmFtZSAlbGlrZSUgIkJvc25pYSIgfiAnQm9zbmlhIGFuZCBIZXJ6ZWdvdmluYScNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAsIGhvbWVfdGVhbV9uYW1lICVsaWtlJSAiVHJpbmlkYWQgYW5kIFRvYmFnbyIgfiAiVHJpbmlkYWQgYW5kIFRvYmFnbyINCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAsIGhvbWVfdGVhbV9uYW1lICVsaWtlJSAiVW5pdGVkIEFyYWIgRW1pcmF0ZXMiIH4gIlVuaXRlZCBBcmFiIEVtaXJhdGVzIg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICwgaG9tZV90ZWFtX25hbWUgJWxpa2UlICJEdXRjaCBFYXN0IEluZGllcyIgfiAiSW5kb25lc2lhIg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICwgVFJVRSB+IGhvbWVfdGVhbV9uYW1lDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAsIGF3YXlfY291bnRyeT1jYXNlX3doZW4oYXdheV90ZWFtX25hbWUgJWxpa2UlICdDaGluYScgfiAiQ2hpbmEiDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLCBhd2F5X3RlYW1fbmFtZT09IkPvv710ZSBkJ0l2b2lyZSIgfiAnSXZvcnkgQ29hc3QnIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICwgYXdheV90ZWFtX25hbWUgJWxpa2UlICJDemVjaCIgfiAnQ3plY2hpYScgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLCBhd2F5X3RlYW1fbmFtZT09IkVuZ2xhbmQiIH4gJ1VuaXRlZCBLaW5nZG9tJw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICwgYXdheV90ZWFtX25hbWU9PSJTY290bGFuZCIgfiAnVW5pdGVkIEtpbmdkb20nDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLCBhd2F5X3RlYW1fbmFtZSAlbGlrZSUgIkdlcm1hbiIgfiAnR2VybWFueScgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLCBhd2F5X3RlYW1fbmFtZSAlbGlrZSUgIklyYW4iIH4gJ0lyYW4nDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLCBhd2F5X3RlYW1fbmFtZSAlbGlrZSUgIktvcmVhIERQUiIgfiAnTm9ydGggS29yZWEnDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLCBhd2F5X3RlYW1fbmFtZSAlbGlrZSUgIktvcmVhIiB+ICdTb3V0aCBLb3JlYScNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAsIGF3YXlfdGVhbV9uYW1lICVsaWtlJSAiSXJlbGFuZCIgfiAnSXJlbGFuZCcNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAsIGF3YXlfdGVhbV9uYW1lICVsaWtlJSAiU2VyYmlhIiB+ICdSZXB1YmxpYyBvZiBTZXJiaWEnDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLCBhd2F5X3RlYW1fbmFtZSAlbGlrZSUgIll1Z29zbGF2aWEiIH4gJ1JlcHVibGljIG9mIFNlcmJpYScNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAsIGF3YXlfdGVhbV9uYW1lICVsaWtlJSAiU292aWV0IFVuaW9uIiB+ICdSdXNzaWEnDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLCBhd2F5X3RlYW1fbmFtZSAlbGlrZSUgIlVTQSIgfiAnVW5pdGVkIFN0YXRlcyBvZiBBbWVyaWNhJw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICwgYXdheV90ZWFtX25hbWUgJWxpa2UlICJXYWxlcyIgfiAnVW5pdGVkIEtpbmdkb20nDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLCBhd2F5X3RlYW1fbmFtZSAlbGlrZSUgIlphaXJlIiB+ICdSZXB1YmxpYyBvZiB0aGUgQ29uZ28nDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLCBhd2F5X3RlYW1fbmFtZSAlbGlrZSUgIkJvc25pYSIgfiAnQm9zbmlhIGFuZCBIZXJ6ZWdvdmluYScNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAsIGF3YXlfdGVhbV9uYW1lICVsaWtlJSAiVHJpbmlkYWQgYW5kIFRvYmFnbyIgfiAiVHJpbmlkYWQgYW5kIFRvYmFnbyINCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAsIGF3YXlfdGVhbV9uYW1lICVsaWtlJSAiVW5pdGVkIEFyYWIgRW1pcmF0ZXMiIH4gIlVuaXRlZCBBcmFiIEVtaXJhdGVzIg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICwgYXdheV90ZWFtX25hbWUgJWxpa2UlICJEdXRjaCBFYXN0IEluZGllcyIgfiAiSW5kb25lc2lhIg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICwgVFJVRSB+IGF3YXlfdGVhbV9uYW1lDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKQ0KDQpgYGANCg0KDQojIyMgVmlzdWFsaXphdGlvbiBvZiBBdmVyYWdlIG51bWJlciBvZiBBd2F5IHRlYW0gZ29hbHMgYW5kIEhvbWUgdGVhbSBnb2FscyBpbiBNYXBzIA0KDQpUaGUgYXZlcmFnZSBudW1iZXIgb2YgZ29hbHMgc2NvcmVkIGJ5IHRoZSBhd2F5IHRlYW0gaXMgdmlzdWFsaXplZCBvbiBhIG1hcCB1c2luZyB0aGUgd29ybGQgc2hhcGVmaWxlIGRhdGEgZnJvbSBbTmF0dXJhbCBFYXJ0aF0oaHR0cHM6Ly93d3cubmF0dXJhbGVhcnRoZGF0YS5jb20vKS4NClRoZSAiYXZlcmFnZV9nb2FscyIgdmFyaWFibGUgY2FsY3VsYXRlZCBmcm9tIHRoZSBXb3JsZCBDdXAgbWF0Y2hlcyBkYXRhIGJ5IGFnZ3JlZ2F0aW5nIHRoZSBob21lIHRlYW0gYW5kIGF3YXkgdGVhbSBnb2FscyBiYXNlZCBvbiB0aGUgY291bnRyeSBhbmQgbWFya2VkIHRoZW0gb24gbWFwIGJ5IGdyb3VwaW5nIHdpdGggY291bnRyeS4NCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nID0gRkFMU0V9DQp3b3JsZF9zaGFwZXMgPC0gcmVhZF9zZigiZGF0YS9uZV8xMTBtX2FkbWluXzBfY291bnRyaWVzL25lXzExMG1fYWRtaW5fMF9jb3VudHJpZXMuc2hwIikNCg0Kd29ybGRjdXBfYXdheV9nb2FscyA8LSB3b3JsZGN1cF9tYXRjaGVzICAlPiUgZ3JvdXBfYnkoYXdheV9jb3VudHJ5KSAlPiVzdW1tYXJpc2UoYXZlcmFnZV9nb2FscyA9IG1lYW4oYXdheV90ZWFtX2dvYWxzKSkNCg0KYXdheV9zcGF0aWFsX2RhdGEgPC0gd29ybGRfc2hhcGVzICU+JQ0KICBsZWZ0X2pvaW4od29ybGRjdXBfYXdheV9nb2FscywgYnkgPSBjKCJBRE1JTiIgPSAiYXdheV9jb3VudHJ5IikpICU+JQ0KICBmaWx0ZXIoSVNPX0EzICE9ICJBVEEiKSANCg0KDQoNCiMgUGxvdCB0aGUgbWFwDQpnZ3Bsb3QoYXdheV9zcGF0aWFsX2RhdGEsIGFlcyhncm91cCA9IEFETUlOKSkgKw0KICBnZW9tX3NmKGFlcyhmaWxsID0gYXZlcmFnZV9nb2FscykpICsNCiAgY29vcmRfc2YoY3JzID0gIitwcm9qPXJvYmluIikgKw0KICBzY2FsZV9maWxsX2dyYWRpZW50KGxvdyA9ICJibHVlIiwgaGlnaCA9ICJyZWQiLCBuYS52YWx1ZSA9ICJncmF5IiwgbmFtZSA9ICJBd2F5IFRlYW0gR29hbHMiKSArDQogIGdndGl0bGUoIkF2ZXJhZ2UgTnVtYmVyIG9mIEF3YXkgVGVhbSBHb2FscyBpbiBXb3JsZCBDdXAgTWF0Y2hlcyIpICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTgsIGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBsZWdlbmQudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwNCiAgICAgICAgbGVnZW5kLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwNCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsDQogICAgICAgIHBhbmVsLmdyaWQgPSBlbGVtZW50X2JsYW5rKCksDQogICAgICAgIGF4aXMudGV4dCA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkNCg0KDQpgYGANCg0KRnJvbSB0aGUgYWJvdmUgbWFwIHdlIGNhbiBzZWUgdGhhdCBHZXJtYW55IHNjb3JlZCBoaWdoZXN0IGF2YWVyYWdlIG51bWJlciBvZiBnb2FscyBpbiB0aGUgd29ybGRjdXAgbWF0Y2hlcyBmb2xsb3dlZCBieSBOZXRoZXJsYW5kcyBhbmQgQnJhemlsLiANCg0KDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nID0gRkFMU0V9DQoNCndvcmxkY3VwX2hvbWVfZ29hbHMgPC0gd29ybGRjdXBfbWF0Y2hlcyAgJT4lIGdyb3VwX2J5KGhvbWVfY291bnRyeSkgJT4lc3VtbWFyaXNlKGF2ZXJhZ2VfZ29hbHMgPSBtZWFuKGhvbWVfdGVhbV9nb2FscykpDQoNCg0KaG9tZV9zcGF0aWFsX2RhdGEgPC0gd29ybGRfc2hhcGVzICU+JQ0KICBsZWZ0X2pvaW4od29ybGRjdXBfaG9tZV9nb2FscywgYnkgPSBjKCJBRE1JTiIgPSAiaG9tZV9jb3VudHJ5IikpICU+JQ0KICBmaWx0ZXIoSVNPX0EzICE9ICJBVEEiKSANCg0KDQoNCiMgUGxvdCB0aGUgbWFwDQogZ2dwbG90KGhvbWVfc3BhdGlhbF9kYXRhLCBhZXMoZ3JvdXAgPSBBRE1JTikpICsNCiAgZ2VvbV9zZihhZXMoZmlsbCA9IGF2ZXJhZ2VfZ29hbHMpKSArDQogICBjb29yZF9zZihjcnMgPSAiK3Byb2o9cm9iaW4iKSArDQogIHNjYWxlX2ZpbGxfZ3JhZGllbnQobG93ID0gImJsdWUiLCBoaWdoID0gInJlZCIsIG5hLnZhbHVlID0gImdyYXkiLCBuYW1lID0gIkhvbWUgVGVhbSBHb2FscyIpICsNCiAgbGFicyh0aXRsZSA9ICJBdmVyYWdlIE51bWJlciBvZiBHb2FscyBTY29yZWQgYnkgSG9tZSBUZWFtcyBpbiBXb3JsZCBDdXAgTWF0Y2hlcyIpICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTgsIGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBsZWdlbmQudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwNCiAgICAgICAgbGVnZW5kLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwNCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsDQogICAgICAgIHBhbmVsLmdyaWQgPSBlbGVtZW50X2JsYW5rKCksDQogICAgICAgIGF4aXMudGV4dCA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkNCg0KYGBgDQoNCkZyb20gdGhlIGFib3ZlIG1hcCB3ZSBjYW4gc2VlIHRoYXQgVHVya2V5IHNjb3JlZCBoaWdoZXN0IGF2YWVyYWdlIG51bWJlciBvZiBnb2FscyBpbiB0aGUgd29ybGRjdXAgbWF0Y2hlcyBmb2xsb3dlZCBieSBIdW5nYXJ5Lg0KDQpBbmFseXplIHRoZSBudW1iZXIgb2YgbWF0Y2hlcyB3b24gYnkgSG9tZSB0ZWFtIGFuZCBBd2F5IHRlYW0gYXMgb3V0Y29tZSBvZiB0aGUgbWF0Y2gNCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmcgPSBGQUxTRSwgZmlnLndpZHRoID0gMTAsIGZpZy5oZWlnaHQgPSA1fQ0KIyBCYXIgY2hhcnQgb2YgbWF0Y2ggb3V0Y29tZQ0KYiA8LSBnZ3Bsb3QobWF0Y2hlc19jb3VudHJpZXNfY2xlYW5lZCwgYWVzKHggPSBvdXRjb21lLCBmaWxsID0gb3V0Y29tZSkpICsNCiAgZ2VvbV9iYXIoIHdpZHRoID0gMC4zKSArDQogIGxhYnModGl0bGUgPSAiTWF0Y2ggT3V0Y29tZSIsDQogICAgICAgeCA9ICJPdXRjb21lIiwgeSA9ICJDb3VudCIpICsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygiSG9tZSBUZWFtIFdpbiIgPSAic3RlZWxibHVlIiwgIkF3YXkgVGVhbSBXaW4iID0gInNhbG1vbiIpKSANCg0KZ2dwbG90bHkoYikgIA0KYGBgDQpUaGUgdG90YWwgbnVtYmVyIG9mIGdvYWxzIHNjb3JlZCBpbiBlYWNoIHllYXIgZnJvbSAxOTMwIHRvIDIwMTQgaXMgdmlzdWFsaXplZCB1c2luZyBhIGJhciBjaGFydC4gVGhlIGNoYXJ0IGRlcGljdHMgdGhlIHRyZW5kIG9mIGdvYWxzIHNjb3JlZCBvdmVyIHRpbWUuDQpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZyA9IEZBTFNFLCBmaWcud2lkdGggPSAxMCwgZmlnLmhlaWdodCA9IDV9DQojIENyZWF0ZSB0aGUgYmFyIGNoYXJ0DQpHb2Fsc19wbG90IDwtIGdncGxvdChtYXRjaGVzX2NvdW50cmllc19jbGVhbmVkLCBhZXMoeCA9IFllYXIsIHkgPSBHb2FscykpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIGZpbGwgPSAic3RlZWxibHVlIikgKw0KICBsYWJzKHRpdGxlID0gIlRvdGFsIEdvYWxzIFNjb3JlZCBFdmVyeSBZZWFyICgxOTMwLTIwMTQpIiwNCiAgICAgICB4ID0gIlllYXIiLCB5ID0gIkdvYWxzIikgKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KZ2dwbG90bHkoR29hbHNfcGxvdCkNCg0KYGBgDQoNCkFzIHdlIGNhbiBzZWUgZnJvbSBhYm92ZSBwbG90LCB0aGVyZSBhcmUgbW9yZSBudW1iZXIgb2YgZ29hbHMgc2NvcmVkIGluIHRoZSB5ZWFyIDIwMDAuIFRoZSB3b3JsZGN1cCBnYW1lcyBoYXMgYmVlbiBoZWxkIGV2ZXJ5IGZvdXIgeWVhcnMsIGV4Y2VwdCBmb3IgdGhlIHllYXJzIDE5NDIgYW5kIDE5NDYsIGR1cmluZyBXb3JsZCBXYXIgSUkuIGR1ZSB0aGF0IHdlIGRvbnQgc2VlIGFueSBkYXRhIGZvciB0aGF0IHBlcmlvZCBpbiB0aGUgcGxvdC4gDQoNCg0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZyA9IEZBTFNFLCBmaWcud2lkdGggPSAxMCwgZmlnLmhlaWdodCA9IDV9DQpXb3JsZGN1cF9nb2Fsc19ieV95ZWFyIDwtIG1hdGNoZXNfY291bnRyaWVzX2NsZWFuZWQgJT4lDQogIGdyb3VwX2J5KFllYXIpICU+JQ0KICBtdXRhdGUodG90YWxfZ29hbHMgPSBzdW0oR29hbHMpKQ0KDQoNCg0KIyBMaW5lIHBsb3Q6IE51bWJlciBvZiBnb2FscyBvdmVyIGRpZmZlcmVudCB5ZWFycw0KDQpsIDwtIGdncGxvdChXb3JsZGN1cF9nb2Fsc19ieV95ZWFyLCBhZXMoeCA9IFllYXIsIHkgPSB0b3RhbF9nb2FscykpICsNCiAgZ2VvbV9saW5lKGNvbG9yPSJibHVlIikgKw0KICB4bGFiKCJZZWFyIikgKyB5bGFiKCJOdW1iZXIgb2YgR29hbHMiKSArDQogIGdndGl0bGUoIk51bWJlciBvZiBHb2FscyBPdmVyIERpZmZlcmVudCBZZWFycyIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQoNCmdncGxvdGx5KGwpDQoNCmBgYA0KDQpUaGUgYWJvdmUgcGxvdCBzaG93cyB1cyB0aGUgbnVtYmVyIG9mIGdvYWxzIHNjb3JlZCBpbiB3b3JsZCBjdXAgb3ZlciB0aGUgeWVhcnMgMTkzMCB0byAyMDE0LiANCg0KDQojIyBIZWFydCBkaXNlYXNlIGRhdGEgc2V0IGFuYWx5c2lzIGFuZCBwcmUtcHJvY2Vzc2luZyANCg0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZyA9IEZBTFNFfQ0KDQpoZWFydF9kYXRhIDwtIGRyb3BfbmEocmVhZC5jc3YoImRhdGEvSGVhcnQuY3N2IilbLCAtMV0pDQoNCnN0cihoZWFydF9kYXRhKQ0KYGBgDQoNClRoZSBkYXRhc2V0IGlzIHByZXByb2Nlc3NlZCBieSBjb252ZXJ0aW5nIHRoZSAiQUhEIiAoaGVhcnQgZGlzZWFzZSBkaWFnbm9zaXMpIGNvbHVtbiBpbnRvIGJpbmFyeSB2YWx1ZXMgKDAgZm9yICJObyIgYW5kIDEgZm9yICJZZXMiKSBiZWNhc3VlIHRoZSB0YXJnZXQgdmFyaWFibGUgaXMgaW4gY2hhcmFjdGVyIGZvcm1hdC4gLg0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZyA9IEZBTFNFfQ0KaGVhcnRfZGF0YSRBSEQ8LWlmZWxzZShoZWFydF9kYXRhJEFIRD09IlllcyIsMSwwKQ0Kc3VtbWFyeShoZWFydF9kYXRhKQ0KYGBgDQoNClNpbmNlIHRoZSBDaGVzdHBhaW4gYW5kIFRoYWwgYXJlIHRoZSBjYXRhZ29yaWNhbCB2YXJpYWJsZXMgY3JlYXRpbmcgZHVtbXkgdmFyaWFibGVzIGludG8gbnVtZXJpYyB2YXJpYWJsZSB0byBmaXQgdGhlIG1vZGVsIHVzaW5nIHRob3NlIHZhcmlhYmxlcy4gDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nID0gRkFMU0V9DQptZGxfZGF0YSA9IGR1bW15X2NvbHMoaGVhcnRfZGF0YSwgc2VsZWN0X2NvbHVtbnM9YyggIkNoZXN0UGFpbiIsICJUaGFsIiksIHJlbW92ZV9zZWxlY3RlZF9jb2x1bW5zPVRSVUUpDQpgYGANCiMjIyBNb2RlbCBGaXR0aW5nIGFuZCBDb2VmZmljaWVudCBQbG90DQoNCkEgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbCBpcyBmaXR0ZWQgdG8gdGhlIGhlYXJ0IGRpc2Vhc2UgZGF0YSB1c2luZyB0aGUgZ2xtKCkgZnVuY3Rpb24uIFRoZSBtb2RlbCdzIGNvZWZmaWNpZW50cyBhbmQgc3VtbWFyeSBzdGF0aXN0aWNzIGFyZSBkaXNwbGF5ZWQuIEFkZGl0aW9uYWxseSwgYSBjb2VmZmljaWVudCBwbG90IGlzIGdlbmVyYXRlZCB0byB2aXN1YWxpemUgdGhlIGNvZWZmaWNpZW50cy4NCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0NCm1kbCA9IGdsbShBSEQgfi4sIGZhbWlseT1iaW5vbWlhbChsaW5rPSdsb2dpdCcpLCBkYXRhPW1kbF9kYXRhKQ0KYGBgDQoNClN1bW1hcnkgZm9yIHRoZSBtb2RlbCBjb2VmZmljaWVudHMsIA0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZyA9IEZBTFNFfQ0Kc3VtbWFyeShtZGwpDQpgYGANCiMjIE1vZGVsIENvZWZmaWNpZW50IHBsb3QNCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nID0gRkFMU0V9DQpkd3Bsb3QobWRsKQ0KYGBgDQoNCkZyb20gYWJvdmUgY29lZmZpY2llbnQgcGxvdCBhbmQgc3VtbWFyeSB3ZSBjYW4gY2xlYXJseSBpZGVudGlmeSBTZXgsIENhLCBDaGVzdFBhaW5fYXN5bXB0b21pYyBhbmQgVGhhbF9ub3JtYWwgYXJlIHRoZSBtb3N0IHNpZ2luaWZpY2FudCB2YXJpYmFsZXMgZm9yIHRoZSBtb2RlbCBwcmVkaWN0aW9uLiANCg0KIyMgQXJlYSBVbmRlciBST0MgY3VydmUNCmBgYHtyfQ0KeV9oYXQgID0gcHJlZGljdChtZGwsIG1kbF9kYXRhKQ0KcHIgPSBwcmVkaWN0aW9uKHlfaGF0LCBtZGxfZGF0YSRBSEQpDQphdWMgPC0gcGVyZm9ybWFuY2UocHIsIG1lYXN1cmUgPSAiYXVjIikNCnRpdGxlID0gcGFzdGUoJ0FVQzogJywgYXVjQHkudmFsdWVzW1sxXV0pDQpwcmYgPC0gcGVyZm9ybWFuY2UocHIsIG1lYXN1cmUgPSAidHByIiwgeC5tZWFzdXJlID0gImZwciIpDQpwbG90KHByZiwgbWFpbj10aXRsZSkNCmBgYA0KDQpUaGUgYXJlYSB1bmRlciB0aGUgcmVjZWl2ZXIgb3BlcmF0aW5nIGNoYXJhY3RlcmlzdGljIChST0MpIGN1cnZlIGlzIGNhbGN1bGF0ZWQgdG8gZXZhbHVhdGUgdGhlIG1vZGVsJ3MgcGVyZm9ybWFuY2UuIFRoZSBST0MgIGlzIGEgbWV0cmljIHVzZWQgdG8gZXZhbHVhdGUgdGhlIHBlcmZvcm1hbmNlIG9mIGEgY2xhc3NpZmljYXRpb24gbW9kZWwuIFRoZSBST0MgY3VydmUgaXMgY3JlYXRlZCBieSBwbG90dGluZyB0aGUgVHJ1ZSBQb3NpdGl2ZSBSYXRlIChTZW5zaXRpdml0eSkgYWdhaW5zdCB0aGUgRmFsc2UgUG9zaXRpdmUgUmF0ZSAoMSAtIFNwZWNpZmljaXR5KSBhdCB2YXJpb3VzIGNsYXNzaWZpY2F0aW9uIHRocmVzaG9sZHMuDQoNCg0KQVVDLVJPQyA+IDAuNTogVGhlIG1vZGVsIHBlcmZvcm1zIGJldHRlciB0aGFuIHJhbmRvbSBndWVzc2luZy4gVGhlIGhpZ2hlciB0aGUgdmFsdWUsIHRoZSBiZXR0ZXIgdGhlIHBlcmZvcm1hbmNlLg0KDQpBcyB3ZSBjYW4gc2VlIHRoaXMgbW9kZWwgZ2VuZXJhdGVkIEFVQy1ST0MgPSAwLjkzMyB3aGljaCBpcyBhIGdvb2QgcHJlZGljdGlvbi4gDQoNCiMgQ29uY2x1c2lvbg0KDQpUaGUgcHJvamVjdCBzdWNjZXNzZnVsbHkgYW5hbHl6ZXMgV29ybGQgQ3VwIG1hdGNoZXMgZGF0YSBhbmQgdmlzdWFsaXplcyB0aGUgYXZlcmFnZSBudW1iZXIgb2YgZ29hbHMgc2NvcmVkIGJ5IHRoZSBob21lIHRlYW0gYW5kIGF3YXkgdGVhbSBvbiBtYXBzLiBBZGRpdGlvbmFsbHksIGl0IHBlcmZvcm1zIG1vZGVsIGZpdHRpbmcgYW5kIHZpc3VhbGl6YXRpb24gb24gaGVhcnQgZGlzZWFzZSBkaWFnbm9zdGljIGRhdGEsIHByb3ZpZGluZyBpbnNpZ2h0cyBpbnRvIGhlYXJ0IGRpc2Vhc2UgcHJlZGljdGlvbi4gVGhlIHZpc3VhbGl6YXRpb25zIGFuZCBhbmFseXNlcyBjb250cmlidXRlIHRvIGEgYmV0dGVyIHVuZGVyc3RhbmRpbmcgb2YgdGhlIGRhdGFzZXRzIGFuZCB0aGVpciBwYXR0ZXJucy4NCg0KDQoNCg0KDQo=